import HashMap from '@ohos.util.HashMap'
import { Utils } from './Utils'

export type Meta = Function

export namespace Meta {
  const ParameterMetaScopeType = "@ParameterMetaScope"
  const MethodMetaScopeType = "@MethodMetaScope"

  export function getMethodMetaScope(target: any, scopeName: string): HashMap<Meta, Array<any>> {
    // scopeName:{
    //     metaName1:[]
    //     metaName2:[]
    // }
    let scope = target[scopeName + MethodMetaScopeType]
    if (!scope) {
      scope = new Map<Function, Array<any>>()
      target[scopeName + MethodMetaScopeType] = scope
    }
    return scope
  }

  export function getParameterMetaScope(target: any, scopeName: string): HashMap<number, HashMap<Meta, Array<any>>> {
    // scopeName:{
    //   index1:{
    //     metaName1:[]
    //     metaName2:[]
    //   }
    //   index2:{
    //     metaName1:[]
    //     metaName2:[]
    //   }
    // }
    let scope = target[scopeName + ParameterMetaScopeType]
    if (!scope) {
      scope = new Map<number, Map<Meta, Array<any>>>()
      target[scopeName + ParameterMetaScopeType] = scope
    }
    return scope
  }


  export function addMethodMetaMeta(target: any, scopeName: string, metaName: Meta, meta: any | null) {
    let scope = this.getMethodMetaScope(target, scopeName)
    let metaScope: Array<any> = scope.get(metaName)
    if (metaScope == null) {
      metaScope = []
      scope.set(metaName, metaScope)
    }
    metaScope.push(meta)
  }

  export function getMethodMeta(target: any, functionName: string, metaName: Meta,): Array<any | null> {
    let scope = this.getMethodMetaScope(target, functionName)
    let metaScope: Array<any> = scope.get(metaName)
    if (metaScope == null) {
      return []
    }
    return metaScope
  }

  export function addParameterMetaMeta(target: any, scopeName: string, index: number, metaName: Meta, meta: any | null) {
    let scope = this.getParameterMetaScope(target, scopeName)
    let indexScope: HashMap<Meta, Array<any | null>> = scope.get(index)
    if (indexScope == null) {
      indexScope = new HashMap<Meta, Array<any | null>>()
      scope.set(index, indexScope)
    }
    let metaScope: Array<any> = indexScope.get(metaName)
    if (metaScope == null) {
      metaScope = []
      indexScope.set(metaName, metaScope)
    }
    metaScope.push(meta)
  }

  export function getParameterMeta(target: any, functionName: string, index: number, metaName: Meta,): Array<any | null> {
    let scope = this.getParameterMetaScope(target, functionName)
    let indexScope: HashMap<Meta, Array<any | null>> = scope.get(index)
    if (indexScope == null) {
      return null
    }
    let metaScope: Array<any> = indexScope.get(metaName)
    if (metaScope == null) {
      return []
    }
    return metaScope
  }

  export function getParameterContextMeta(context: ParameterMetaContext, metaName: Meta): Array<any | null> {
    return this.getParameterMeta(context.functionContext.target, context.functionContext.methodName, context.index, metaName)
  }

  export function createMethodMeta<T extends Meta>(): T {
    const meta = (value: any, propertyName: string, descriptor: PropertyDescriptor): MethodDecorator => {
      if (!Utils.isUndefined(propertyName) && !Utils.isUndefined(descriptor)) {
        Meta.addMethodMetaMeta(value, propertyName, meta, null)
        return
      }
      return (target: any, propertyName: string, descriptor: PropertyDescriptor) => {
        if (typeof value === 'undefined') {
          value = null
        }
        Meta.addMethodMetaMeta(target, propertyName, meta, value)
      }
    }
    return meta as unknown as T
  }

  export function createParameterMeta<T extends Meta>(): T {
    const meta = (value: any, propertyName: string, index: number): ParameterDecorator => {
      //这逻辑是没有参数的 如@Meta
      if (!Utils.isUndefined(propertyName) && !Utils.isUndefined(index)) {
        Meta.addParameterMetaMeta(value, propertyName, index, meta, null)
        return
      }
      //这逻辑是有参数的 如@Meta(xx) @Meta()
      return (target: any, propertyName: string, index: number) => {
        if (typeof value === 'undefined') {
          value = null
        }
        Meta.addParameterMetaMeta(target, propertyName, index, meta, value)
      }
    }
    return meta as unknown as T
  }
}

export class ParameterMetaContext {
  constructor(public functionContext: MethodMetaContext, public index: number, public value: any) {
  }

  getMeta(metaName: Meta): any[] {
    return Meta.getParameterMeta(
      this.functionContext.target,
      this.functionContext.methodName,
      this.index,
      metaName
    )
  }

  getMetaScope(): HashMap<Meta, any[]> | null {
    return Meta.getParameterMetaScope(
      this.functionContext.target,
      this.functionContext.methodName
    ).get(this.index)
  }
}

export class MethodMetaContext {
  parameterSize: number = 0
  parameterMetaContexts: ParameterMetaContext[] = []

  constructor(public target: any, public methodName: string, args: any[]) {
    args?.forEach((value, index) => {
      this.parameterMetaContexts.push(new ParameterMetaContext(this, index, value))
    })
    this.parameterSize = this.parameterMetaContexts.length
  }

  getParameterMetaContext(index: number): ParameterMetaContext {
    return this.parameterMetaContexts[index]
  }

  getMeta(metaName: Meta): any[] {
    return Meta.getMethodMeta(
      this.target,
      this.methodName,
      metaName
    )
  }

  getMetaScope(): HashMap<Meta, any[]> | null {
    return Meta.getMethodMetaScope(
      this.target,
      this.methodName
    )
  }

  getParameterMetaScope(): HashMap<number, HashMap<Meta, any[]>> {
    return Meta.getParameterMetaScope(
      this.target,
      this.methodName
    )
  }
}
